package com.zycfc.zsf.boot.core.util;

import com.zycfc.zsf.boot.core.domain.*;
import com.zycfc.zsf.boot.util.text.*;
import com.zycfc.zsf.boot.core.enums.*;
import io.swagger.annotations.*;
import java.lang.reflect.*;
import java.math.*;
import com.zycfc.zsf.boot.util.time.*;
import java.util.*;
import com.zycfc.zsf.boot.util.collection.*;

public class BeanComparator
{
    private static final String GET_METHOD_PRE = "get";
    private static final String ID_FIELD_NAME = "id";
    private static final String SERIALVERSIONUID_FIELD_NAME = "serialVersionUID";
    private static final String ORI_BEAN_REMANENT_FIELDS_KEY = "oriBeanRemanentFields";
    private static final String TARGET_BEAN_REMANENT_FIELDS_KEY = "targetBeanRemanentFields";
    private static final String BEAN_COMMON_FIELDS_KEY = "beanCommonFields";
    private static final String IDGENERATOR_BEAN_NAME = "idGenerator";
    private static final String ORIGBEANS_AND_TARGETBEANS_CAN_NOT_BOTH_NULL = "origBeans\u548ctargetBeans\u4e0d\u80fd\u90fd\u4e3a\u7a7a\u96c6\u5408\uff01";
    private static final String ORIGBEAN_AND_TARGETBEAN_CAN_NOT_BOTH_NULL = "origBean\u548ctargetBean\u4e0d\u80fd\u90fd\u4e3a\u7a7a\u5bf9\u8c61\uff01";
    private static final String BLANK_STR = "";
    
    public static <O, T> List<BeanCompareResultModel> compare(final List<O> origBeans, final List<T> targetBeans, final List<String> joinConditions, final List<String> excludeFields, String batchSeqNo) throws Exception {
        if (CheckUtil.isEmpty((Object)origBeans) && CheckUtil.isEmpty((Object)targetBeans)) {
            throw new IllegalArgumentException("origBeans\u548ctargetBeans\u4e0d\u80fd\u90fd\u4e3a\u7a7a\u96c6\u5408\uff01");
        }
        final List<BeanCompareResultModel> resultModelList = new ArrayList<BeanCompareResultModel>();
        batchSeqNo = (CheckUtil.isEmpty((Object)batchSeqNo) ? UuidUtils.getUuid() : batchSeqNo);
        final Map<String, Method> origMethodMap = CheckUtil.isEmpty((Object)origBeans) ? null : getMethodMap(origBeans.get(0).getClass());
        final Map<String, Method> targetMethodMap = CheckUtil.isEmpty((Object)targetBeans) ? null : getMethodMap(targetBeans.get(0).getClass());
        if (CheckUtil.isEmpty((Object)origBeans)) {
            resultModelList.addAll(beansToBeanCompareResultModels(targetBeans, UdpTypeEnum.I, origMethodMap, targetMethodMap, excludeFields, batchSeqNo));
            return resultModelList;
        }
        if (CheckUtil.isEmpty((Object)targetBeans)) {
            resultModelList.addAll(beansToBeanCompareResultModels(origBeans, UdpTypeEnum.D, origMethodMap, targetMethodMap, excludeFields, batchSeqNo));
            return resultModelList;
        }
        final BeanJoinResultModel<O, T> joinResultModel = joinBeans(origBeans, targetBeans, joinConditions, origMethodMap, targetMethodMap);
        final List<O> origRemanentBeans = joinResultModel.getOrigRemanentBeans();
        if (!CheckUtil.isEmpty((Object)origRemanentBeans)) {
            resultModelList.addAll(beansToBeanCompareResultModels(origRemanentBeans, UdpTypeEnum.D, origMethodMap, targetMethodMap, excludeFields, batchSeqNo));
        }
        final List<T> targetRemanentBeans = joinResultModel.getTargetRemanentBeans();
        if (!CheckUtil.isEmpty((Object)targetRemanentBeans)) {
            resultModelList.addAll(beansToBeanCompareResultModels(targetRemanentBeans, UdpTypeEnum.I, origMethodMap, targetMethodMap, excludeFields, batchSeqNo));
        }
        final Map<O, T> joinResultMap = joinResultModel.getJoinResultMap();
        for (final Map.Entry<O, T> entry : joinResultMap.entrySet()) {
            final List models = compare(entry.getKey(), entry.getValue(), excludeFields, origMethodMap, targetMethodMap, batchSeqNo);
            resultModelList.addAll(models);
        }
        return resultModelList;
    }
    
    public static <O, T> List<BeanCompareResultModel> compare(final O oriBean, final T targetBean, final List<String> excludeFields, String batchSeqNo) throws Exception {
        if (oriBean == null && targetBean == null) {
            throw new IllegalArgumentException("origBean\u548ctargetBean\u4e0d\u80fd\u90fd\u4e3a\u7a7a\u5bf9\u8c61\uff01");
        }
        batchSeqNo = (CheckUtil.isEmpty((Object)batchSeqNo) ? UuidUtils.getUuid() : batchSeqNo);
        final Map<String, Method> origMethodMap = (oriBean != null) ? getMethodMap(oriBean.getClass()) : null;
        final Map<String, Method> targetMethodMap = (targetBean != null) ? getMethodMap(targetBean.getClass()) : null;
        return compare(oriBean, targetBean, excludeFields, origMethodMap, targetMethodMap, batchSeqNo);
    }
    
    private static <O, T> List<BeanCompareResultModel> compare(final O oriBean, final T targetBean, final List<String> excludeFields, final Map<String, Method> origMethodMap, final Map<String, Method> targetMethodMap, final String batchSeqNo) throws IllegalAccessException, NoSuchFieldException, InvocationTargetException {
        final List<BeanCompareResultModel> resultModelList = new ArrayList<BeanCompareResultModel>();
        if (oriBean == null) {
            final List<String> targetFields = getFieldNameList(targetBean.getClass());
            if (!CheckUtil.isEmpty((Object)excludeFields)) {
                targetFields.removeAll(excludeFields);
            }
            final List<BeanCompareResultModel> targetModels = assembleBeanCompareResultModel(oriBean, targetBean, targetFields, UdpTypeEnum.I, UdpTypeEnum.I, origMethodMap, targetMethodMap, batchSeqNo);
            resultModelList.addAll(targetModels);
            return resultModelList;
        }
        if (targetBean == null) {
            final List<String> oriFields = getFieldNameList(oriBean.getClass());
            if (!CheckUtil.isEmpty((Object)excludeFields)) {
                oriFields.removeAll(excludeFields);
            }
            final List<BeanCompareResultModel> oriModels = assembleBeanCompareResultModel(oriBean, targetBean, oriFields, UdpTypeEnum.D, UdpTypeEnum.D, origMethodMap, targetMethodMap, batchSeqNo);
            resultModelList.addAll(oriModels);
            return resultModelList;
        }
        final Map<String, List<String>> fieldMap = compareBeanFields(oriBean.getClass(), targetBean.getClass(), excludeFields);
        final List<String> beanCommonFields = fieldMap.get("beanCommonFields");
        final List<String> oriBeanRemanentFields = fieldMap.get("oriBeanRemanentFields");
        final List<String> targetBeanRemanentFields = fieldMap.get("targetBeanRemanentFields");
        final List<BeanCompareResultModel> oriBeanRemanentModels = assembleBeanCompareResultModel(oriBean, targetBean, oriBeanRemanentFields, UdpTypeEnum.D, UdpTypeEnum.U, origMethodMap, targetMethodMap, batchSeqNo);
        resultModelList.addAll(oriBeanRemanentModels);
        final List<BeanCompareResultModel> targetBeanRemanentModels = assembleBeanCompareResultModel(oriBean, targetBean, targetBeanRemanentFields, UdpTypeEnum.I, UdpTypeEnum.U, origMethodMap, targetMethodMap, batchSeqNo);
        resultModelList.addAll(targetBeanRemanentModels);
        final List<BeanCompareResultModel> beanCommonModels = assembleBeanCompareResultModel(oriBean, targetBean, beanCommonFields, UdpTypeEnum.U, UdpTypeEnum.U, origMethodMap, targetMethodMap, batchSeqNo);
        resultModelList.addAll(beanCommonModels);
        return resultModelList;
    }
    
    private static <O, T> List<BeanCompareResultModel> assembleBeanCompareResultModel(final O oriBean, final T targetBean, final List<String> fieldNames, final UdpTypeEnum updType, final UdpTypeEnum objectUpdType, final Map<String, Method> origMethodMap, final Map<String, Method> targetMethodMap, final String batchSeqNo) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        final List<BeanCompareResultModel> resultModelList = new ArrayList<BeanCompareResultModel>();
        for (final String fieldName : fieldNames) {
            final BeanCompareResultModel model = new BeanCompareResultModel();
            Field field = null;
            try {
                field = (UdpTypeEnum.I.equals(updType) ? targetBean.getClass().getDeclaredField(fieldName) : oriBean.getClass().getDeclaredField(fieldName));
            }
            catch (NoSuchFieldException e) {
                try {
                    field = (UdpTypeEnum.I.equals(updType) ? targetBean.getClass().getSuperclass().getDeclaredField(fieldName) : oriBean.getClass().getSuperclass().getDeclaredField(fieldName));
                }
                catch (NoSuchFieldException e2) {
                    field = (UdpTypeEnum.I.equals(updType) ? targetBean.getClass().getSuperclass().getSuperclass().getDeclaredField(fieldName) : oriBean.getClass().getSuperclass().getSuperclass().getDeclaredField(fieldName));
                }
            }
            if (UdpTypeEnum.U.equals(updType)) {
                final Object oldValObj = origMethodMap.get(field.getName()).invoke(oriBean, new Object[0]);
                final Object newValObj = targetMethodMap.get(field.getName()).invoke(targetBean, new Object[0]);
                if (compareBigValue(oldValObj, newValObj)) {
                    continue;
                }
            }
            model.setOldVal((oriBean != null && !UdpTypeEnum.I.equals(updType)) ? getFieldValue(oriBean, origMethodMap, field.getName()) : null);
            model.setNewVal((targetBean != null && !UdpTypeEnum.D.equals(updType)) ? getFieldValue(targetBean, targetMethodMap, field.getName()) : null);
            final String oldValue = model.getOldVal();
            final String newValue = model.getNewVal();
            if ((oldValue != null || newValue != null) && (oldValue == null || (!oldValue.equals(newValue) && (!"".equals(oldValue.trim()) || newValue != null)))) {
                if (newValue != null) {
                    if (newValue.equals(oldValue)) {
                        continue;
                    }
                    if ("".equals(newValue.trim()) && oldValue == null) {
                        continue;
                    }
                }
                final ApiModelProperty fieldAnnotation = field.getAnnotation(ApiModelProperty.class);
                model.setBatchSeqNo(batchSeqNo);
                model.setBizId((oriBean != null) ? getFieldValue(oriBean, origMethodMap, "id") : getFieldValue(targetBean, targetMethodMap, "id"));
                model.setDesType((fieldAnnotation != null) ? fieldAnnotation.reference() : null);
                model.setFldId(field.getName());
                model.setFldName((fieldAnnotation != null) ? fieldAnnotation.value() : null);
                model.setUpdType(updType.name());
                model.setObjectUpdType(objectUpdType.name());
                resultModelList.add(model);
            }
        }
        return resultModelList;
    }
    
    private static boolean compareBigValue(final Object oldValObj, final Object newValObj) {
        return oldValObj != null && newValObj != null && oldValObj instanceof BigDecimal && newValObj instanceof BigDecimal && ((BigDecimal)oldValObj).compareTo((BigDecimal)newValObj) == 0;
    }
    
    private static <C> String getFieldValue(final C bean, final Map<String, Method> methodMap, final String fieldName) throws InvocationTargetException, IllegalAccessException {
        final Object obj = methodMap.get(fieldName).invoke(bean, new Object[0]);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Date) {
            return DateUtils.ISO_ON_DATE_FORMAT.format(obj);
        }
        return String.valueOf(obj);
    }
    
    public static <O, T> Map<String, List<String>> compareBeanFields(final Class<O> oriCls, final Class<T> targetCls, final List<String> excludeFields) {
        final Map<String, List<String>> fieldMap = new LinkedHashMap<String, List<String>>();
        final List<String> oriBeanRemanentFields = new ArrayList<String>();
        final List<String> targetBeanRemanentFields = new ArrayList<String>();
        final List<String> beanCommonFields = new ArrayList<String>();
        final List<String> oriFieldNames = getFieldNameList(oriCls);
        final List<String> targetFieldNames = getFieldNameList(targetCls);
        if (!CheckUtil.isEmpty((Object)excludeFields)) {
            oriFieldNames.removeAll(excludeFields);
            targetFieldNames.removeAll(excludeFields);
        }
        beanCommonFields.addAll(oriFieldNames);
        beanCommonFields.retainAll(targetFieldNames);
        oriBeanRemanentFields.addAll(oriFieldNames);
        oriBeanRemanentFields.removeAll(beanCommonFields);
        targetBeanRemanentFields.addAll(targetFieldNames);
        targetBeanRemanentFields.removeAll(beanCommonFields);
        fieldMap.put("oriBeanRemanentFields", oriBeanRemanentFields);
        fieldMap.put("targetBeanRemanentFields", targetBeanRemanentFields);
        fieldMap.put("beanCommonFields", beanCommonFields);
        return fieldMap;
    }
    
    private static <T> List<String> getFieldNameList(final Class<T> tCls) {
        List<String> fieldNameList = null;
        final List<Field> fieldList = (List<Field>)ArrayUtils.toArrayList((Object[])tCls.getDeclaredFields());
        fieldNameList = getFieldNameList(fieldList);
        fieldNameList = ((fieldNameList == null) ? new ArrayList<String>() : fieldNameList);
        final Field[] superFields = tCls.getSuperclass().getDeclaredFields();
        if (!CheckUtil.isEmpty((Object)superFields)) {
            final List<Field> superFieldList = (List<Field>)ArrayUtils.toArrayList((Object[])superFields);
            final List<String> superFieldNameList = getFieldNameList(superFieldList);
            for (final String superFieldName : superFieldNameList) {
                if (!fieldNameList.contains(superFieldName)) {
                    fieldNameList.add(superFieldName);
                }
            }
        }
        final Field[] grandFields = tCls.getSuperclass().getSuperclass().getDeclaredFields();
        if (!CheckUtil.isEmpty((Object)grandFields)) {
            final List<Field> grandFieldList = (List<Field>)ArrayUtils.toArrayList((Object[])grandFields);
            final List<String> grandFieldNameList = getFieldNameList(grandFieldList);
            for (final String grandFieldName : grandFieldNameList) {
                if (!fieldNameList.contains(grandFieldName)) {
                    fieldNameList.add(grandFieldName);
                }
            }
        }
        if (fieldNameList.contains("serialVersionUID")) {
            fieldNameList.remove("serialVersionUID");
        }
        return fieldNameList;
    }
    
    private static List<String> getFieldNameList(final List<Field> list) {
        if (CheckUtil.isEmpty((Object)list)) {
            return null;
        }
        final List<String> fieldNameList = new ArrayList<String>();
        for (final Field field : list) {
            fieldNameList.add(field.getName());
        }
        return fieldNameList;
    }
    
    private static <B> List<BeanCompareResultModel> beansToBeanCompareResultModels(final List<B> beans, final UdpTypeEnum updType, final Map<String, Method> origMethodMap, final Map<String, Method> targetMethodMap, final List<String> excludeFields, final String batchSeqNo) throws InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        final List<BeanCompareResultModel> resultModelList = new ArrayList<BeanCompareResultModel>();
        final List<String> fieldNameList = getFieldNameList(beans.get(0).getClass());
        if (!CheckUtil.isEmpty((Object)excludeFields)) {
            fieldNameList.removeAll(excludeFields);
        }
        for (final B bean : beans) {
            List<BeanCompareResultModel> models = null;
            if (UdpTypeEnum.D.equals(updType)) {
                models = assembleBeanCompareResultModel(bean, (Object)null, fieldNameList, updType, updType, origMethodMap, targetMethodMap, batchSeqNo);
            }
            else if (UdpTypeEnum.I.equals(updType)) {
                models = assembleBeanCompareResultModel((Object)null, bean, fieldNameList, updType, updType, origMethodMap, targetMethodMap, batchSeqNo);
            }
            resultModelList.addAll(models);
        }
        return resultModelList;
    }
    
    private static <O, T> BeanJoinResultModel<O, T> joinBeans(final List<O> origBeans, final List<T> targetBeans, final List<String> joinConditions, final Map<String, Method> origMethodMap, final Map<String, Method> targetMethodMap) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Map<O, T> joinResultMap = new LinkedHashMap<O, T>();
        final List<O> origRemanentBeans = new ArrayList<O>();
        final List<T> targetRemanentBeans = new ArrayList<T>();
        boolean matchFlag = false;
        for (final O o : origBeans) {
            for (final T t : targetBeans) {
                if (joinBean(o, t, joinConditions, origMethodMap, targetMethodMap)) {
                    joinResultMap.put(o, t);
                    matchFlag = true;
                }
            }
            if (!matchFlag) {
                origRemanentBeans.add(o);
            }
            matchFlag = false;
        }
        targetRemanentBeans.addAll((Collection<? extends T>)targetBeans);
        targetRemanentBeans.removeAll(joinResultMap.values());
        final BeanJoinResultModel beanJoinResultModel = new BeanJoinResultModel((Map<O, T>)joinResultMap, (List<O>)origRemanentBeans, (List<T>)targetRemanentBeans);
        return (BeanJoinResultModel<O, T>)beanJoinResultModel;
    }
    
    private static <O, T> boolean joinBean(final O origBean, final T targetBean, final List<String> joinConditions, final Map<String, Method> origMethodMap, final Map<String, Method> targetMethodMap) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object oriFieldValue = null;
        Object targetFieldValue = null;
        for (final String fieldName : joinConditions) {
            oriFieldValue = origMethodMap.get(fieldName).invoke(origBean, new Object[0]);
            targetFieldValue = targetMethodMap.get(fieldName).invoke(targetBean, new Object[0]);
            if ((oriFieldValue == null && targetFieldValue != null) || (oriFieldValue != null && targetFieldValue == null) || (oriFieldValue != null && !oriFieldValue.equals(targetFieldValue))) {
                return false;
            }
        }
        return true;
    }
    
    private static <C> Map<String, Method> getMethodMap(final Class<C> tCls) throws NoSuchMethodException {
        final Map<String, Method> methodMap = new LinkedHashMap<String, Method>();
        final List<String> fieldNameList = getFieldNameList(tCls);
        for (final String fieldName : fieldNameList) {
            final String methodName = new StringBuffer("get").append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1)).toString();
            final Method method = tCls.getMethod(methodName, (Class<?>[])new Class[0]);
            methodMap.put(fieldName, method);
        }
        return methodMap;
    }
    
    private static class BeanJoinResultModel<O, T>
    {
        private Map<O, T> joinResultMap;
        private List<O> origRemanentBeans;
        private List<T> targetRemanentBeans;
        
        public BeanJoinResultModel() {
        }
        
        public BeanJoinResultModel(final Map<O, T> joinResultMap, final List<O> origRemanentBeans, final List<T> targetRemanentBeans) {
            this.joinResultMap = joinResultMap;
            this.origRemanentBeans = origRemanentBeans;
            this.targetRemanentBeans = targetRemanentBeans;
        }
        
        public Map<O, T> getJoinResultMap() {
            return this.joinResultMap;
        }
        
        public void setJoinResultMap(final Map<O, T> joinResultMap) {
            this.joinResultMap = joinResultMap;
        }
        
        public List<O> getOrigRemanentBeans() {
            return this.origRemanentBeans;
        }
        
        public void setOrigRemanentBeans(final List<O> origRemanentBeans) {
            this.origRemanentBeans = origRemanentBeans;
        }
        
        public List<T> getTargetRemanentBeans() {
            return this.targetRemanentBeans;
        }
        
        public void setTargetRemanentBeans(final List<T> targetRemanentBeans) {
            this.targetRemanentBeans = targetRemanentBeans;
        }
    }
}
